import socket
import os
import hashlib
import json
import threading


class FileSyncServer:
    def __init__(self, host='localhost', port=8888):
        self.host = host
        self.port = port
        self.server_socket = None

    def start_server(self):
        """启动服务器"""
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind((self.host, self.port))
        self.server_socket.listen(5)
        print(f"服务器启动，监听 {self.host}:{self.port}")

        while True:
            client_socket, address = self.server_socket.accept()
            print(f"客户端连接: {address}")

            # 为每个客户端创建一个新线程
            client_thread = threading.Thread(target=self.handle_client, args=(client_socket,))
            client_thread.start()

    def handle_client(self, client_socket):
        """处理客户端请求"""
        try:
            while True:
                # 接收客户端发送的命令
                command = client_socket.recv(1024).decode('utf-8')
                if not command:
                    break

                print(f"收到命令: {command}")

                if command == "SYNC":
                    self.sync_directory(client_socket)
                elif command == "GET_FILE_INFO":
                    self.get_file_info(client_socket)
                else:
                    client_socket.send(b"UNKNOWN_COMMAND")

        except Exception as e:
            print(f"处理客户端时出错: {e}")
        finally:
            client_socket.close()

    def get_file_info(self, client_socket):
        """获取目录结构信息"""
        try:
            # 获取当前工作目录
            current_dir = os.getcwd()

            # 递归获取所有文件和目录信息
            file_tree = self.get_directory_structure(current_dir)

            # 发送文件树信息给客户端
            file_tree_json = json.dumps(file_tree, ensure_ascii=False, separators=(',', ':'))
            client_socket.send(file_tree_json.encode('utf-8'))

        except Exception as e:
            print(f"获取文件信息时出错: {e}")
            client_socket.send(b"ERROR")

    def get_directory_structure(self, root_path):
        """递归获取目录结构"""
        structure = {
            'name': os.path.basename(root_path),
            'path': root_path,
            'is_dir': True,
            'children': []
        }

        try:
            items = os.listdir(root_path)
            for item in items:
                item_path = os.path.join(root_path, item)
                if os.path.isdir(item_path):
                    # 递归处理子目录
                    structure['children'].append(self.get_directory_structure(item_path))
                else:
                    # 处理文件
                    file_info = {
                        'name': item,
                        'path': item_path,
                        'is_dir': False,
                        'size': os.path.getsize(item_path),
                        'hash': self.calculate_file_hash(item_path)
                    }
                    structure['children'].append(file_info)
        except Exception as e:
            print(f"读取目录结构时出错: {e}")

        return structure

    def calculate_file_hash(self, file_path):
        """计算文件的MD5哈希值"""
        hash_md5 = hashlib.md5()
        try:
            with open(file_path, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash_md5.update(chunk)
            return hash_md5.hexdigest()
        except Exception as e:
            print(f"计算文件哈希时出错: {e}")
            return ""

    def sync_directory(self, client_socket):
        """同步目录结构和文件"""
        try:
            # 获取当前工作目录的文件树
            current_dir = os.getcwd()
            file_tree = self.get_directory_structure(current_dir)

            # 发送文件树信息给客户端
            file_tree_json = json.dumps(file_tree, ensure_ascii=False, separators=(',', ':'))
            client_socket.send(file_tree_json.encode('utf-8'))

            # 接收客户端的响应，确认开始同步
            response = client_socket.recv(1024).decode('utf-8')
            if response != "READY":
                return

            # 同步文件内容
            self.sync_files(client_socket, file_tree)

        except Exception as e:
            print(f"目录同步时出错: {e}")
            client_socket.send(b"SYNC_ERROR")

    def sync_files(self, client_socket, file_tree):
        """同步文件内容"""
        try:
            # 遍历文件树，发送每个文件
            self.sync_files_recursive(client_socket, file_tree)
        except Exception as e:
            print(f"同步文件时出错: {e}")

    def sync_files_recursive(self, client_socket, file_tree):
        """递归同步文件"""
        for child in file_tree['children']:
            if not child['is_dir']:
                self.send_file(client_socket, child)
            else:
                # 递归处理子目录
                self.sync_files_recursive(client_socket, child)

    def send_file(self, client_socket, file_info):
        """发送单个文件"""
        try:
            file_path = file_info['path']

            # 发送文件信息
            file_header = {
                'name': file_info['name'],
                'size': file_info['size'],
                'hash': file_info['hash']
            }
            client_socket.send(json.dumps(file_header, separators=(',', ':')).encode('utf-8'))

            # 等待客户端确认接收准备
            response = client_socket.recv(1024).decode('utf-8')
            if response != "READY_TO_RECEIVE":
                return

            # 发送文件内容（二进制）
            with open(file_path, 'rb') as f:
                while True:
                    data = f.read(4096)
                    if not data:
                        break
                    client_socket.send(data)

            # 发送文件传输完成标志
            # client_socket.send(b"FILE_TRANSFER_COMPLETE")  # bug: 这行代码多余，如果不去掉，会导致客户端最后一个文件接受尾部多一个FILE——TRANSFER——COMPLETE，导致客户端hash计算错误。

        except Exception as e:
            print(f"发送文件 {file_path} 时出错: {e}")
            client_socket.send(b"SEND_ERROR")


if __name__ == "__main__":
    server = FileSyncServer()
    try:
        server.start_server()
    except KeyboardInterrupt:
        print("服务器关闭")
        if server.server_socket:
            server.server_socket.close()